{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "# Python代码结构\n",
    "\n",
    "## 错误和异常\n",
    "\n",
    "### 什么是异常\n",
    "\n",
    "#### 错误\n",
    "\n",
    "错误可以分为两种，语法上的和逻辑上的，当检测到一个错误时，Python解释器会指出当前流已经无法继续执行下去。这个时候就出现了异常(Exception)。\n",
    "\n",
    "#### 异常\n",
    "\n",
    "对异常的最好描述是：它是因为程序出现了错误而在正常控制流之外采取的行为，这个行为分为两个阶段。\n",
    "\n",
    "* 引起异常发生的错误\n",
    "* 检测（和采取可能措施）阶段\n",
    "\n",
    "当程序运行过程中遇到错误时就会抛出一个异常（异常也可以由程序员主动触发）。其可以被异常控制语句捕捉并予以处理。异常如果不能被捕捉和处理，就会以错误的形式呈现出来。\n",
    "\n",
    "在类C语言（包括C语言）的程序设计中，程序员必须尽量考虑到各种错误情况，从而通过代码设计增强代码健壮性，然而总是有些特殊的会引发错误的情况无法考虑到，这就导致了错误发生时，只能眼睁睁的看着程序崩溃掉。异常的引入改变了这一情况，错误引发的异常可以被分类捕捉并在程序主流程以外加以处理，异常处理后，程序重新回到主流程中运行。对于像交换机、路由器这样的需要不间断运行的设备，出现错误并挂掉是不允许的，异常处理便显得极为重要。\n",
    "\n",
    "\n",
    "### Python中的几种常见异常\n",
    "\n",
    "* `NameError`：尝试访问一个未声明的变量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "foo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "* `ZeroDivisionError`：除数为0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "1/0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "* `SyntaxError`：解释器语法错误"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "for"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "* `IndexError`：请求的索引超出序列范围"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "alist = [0, 1, 2]\n",
    "alist[4]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "* `KeyError`：请求一个不存在的字典键"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "adict = {'a': 1, 'b': 2}\n",
    "adict['c']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "* `FileNotFoundError`：输入/输出错误"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "afile = open('not_exists.txt')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "* `AttributeError`：尝试访问未知的对象属性"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "'abc'.good()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "### 检测和处理异常\n",
    "\n",
    "#### `try-except`语句\n",
    "\n",
    "语法：\n",
    "\n",
    "```\n",
    "try:\n",
    "    try_suite   # 监控这里的异常\n",
    "except exceptiontype as name:\n",
    "    except_suite  # 异常处理代码\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    f = open('not_exists.txt')\n",
    "except FileNotFoundError as err:\n",
    "    print('File does not exist.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "程序执行时，尝试执行`try`中的代码，如果代码块中没有发现任何异常，则忽略`except`代码块中的内容，反之，如果发生的异常和`except`中要捕捉的异常类型相同。则调用其代码块中的代码对异常进行处理。处理后，返回程序的主流程中。\n",
    "\n",
    "如果`try`中产生的异常在`except`中无法被捕捉（类型不匹配），异常就会被递交至上一级，也就是由该段代码的调用者去处理。如果最后还是无法解决的话，就会出现错误，导致程序崩溃。\n",
    "\n",
    "#### `try-except-except-...`语句（多`except`）\n",
    "\n",
    "语法：\n",
    "\n",
    "```\n",
    "try:\n",
    "    try_suite   # 监控这里的异常\n",
    "except exceptiontype1 as name1:\n",
    "    except_suite1  # 异常处理代码\n",
    "except exceptiontype2 as name2:\n",
    "    except_suite2  # 异常处理代码\n",
    "...\n",
    "```\n",
    "\n",
    "有时候`try`代码中可能发生的异常有多种类型，我们可以针对不同类型的异常分类捕捉并予以处理。下面的例子定义了一个浮点类型转换函数，对于各种非法参数可以返回“标准的waring”："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def safe_float(obj):\n",
    "    try:\n",
    "        retval = float(obj)\n",
    "    except ValueError:  # 异常：类型正确，值不正确\n",
    "        retval = 'could not convert non-number to float'\n",
    "    except TypeError:   # 异常：类型不正确\n",
    "        retval = 'object type cannot be converted to float'\n",
    "    return retval\n",
    "\n",
    "print(safe_float('xyz'))\n",
    "print(safe_float(()))\n",
    "print(safe_float(200))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "#### 处理多个异常的`except`语句\n",
    "\n",
    "语法：\n",
    "```\n",
    "try:\n",
    "    try_suite\n",
    "except (Exc1,[, Exc2[, ... ExcN]])[as reason]:\n",
    "    suite_for_exception1_to_excN\n",
    "```\n",
    "\n",
    "这种语法使得我们可以对多种异常类型使用相同的处理方法。使用这种语法重写的`safe_float()`如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def safe_float(obj):\n",
    "    try:\n",
    "        retval = float(obj)\n",
    "    except (ValueError, TypeError):\n",
    "        retval = 'arguments must be a number or numeric string'\n",
    "    return retval\n",
    "\n",
    "print(safe_float('xyz'))\n",
    "print(safe_float(()))\n",
    "print(safe_float(200))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "现在，对于不同的类型错误返回的是相同的错误提示字符串。\n",
    "\n",
    "#### 捕获所有异常\n",
    "\n",
    "语法：\n",
    "\n",
    "```\n",
    "try:\n",
    "    ...\n",
    "except [Exception] [as reason]:\n",
    "    ...\n",
    "```\n",
    "\n",
    "在异常的继承树结构中，Exception是所有错误引发的异常的基类。当然，这里不包括`SystemExit`（当前应用程序需要退出）、 `KeyboardInterupt`（用户按下了Ctrl+C键）这两种异常。\n",
    "\n",
    "真正的异常基类是`BaseException`，其有3个子类，分别为`Exception`，`SystemExit`，`KeyboardInterrupt`。\n",
    "\n",
    "所以，如果要真正捕捉“所有”的异常，`except`语句应该写：`except BaseException as e`:\n",
    "\n",
    "所以，通常的代码框架以如下写法进行：\n",
    "\n",
    "```\n",
    "try:\n",
    "    ...\n",
    "except (KeyboardInterrupt, SystemExit):\n",
    "    #用户希望退出\n",
    "    raise   # 将异常上交给 caller\n",
    "except Exception:\n",
    "    # 处理真正的错误\n",
    "```\n",
    "\n",
    "#### 异常参数\n",
    "\n",
    "异常有参数，前面的例子中，在`except`后你经常看到有个`e`，这个`e`就是异常参数，它是指示了异常原因的一个字符串。虽然在前面的很多例子中异常参数都被忽略了，但在实际编码过程中，将参数字符串和自己设定的错误信息一起输出是一个好的习惯。\n",
    "\n",
    "```\n",
    "...\n",
    "except ValueError as e:\n",
    "    print('invalid value', e)\n",
    "```\n",
    "\n",
    "#### `else`子句\n",
    "\n",
    "```\n",
    "try:\n",
    "    ...\n",
    "except:\n",
    "    ...\n",
    "else:\n",
    "    ...\n",
    "```\n",
    "\n",
    "`try`代码块中没有异常检测到时，执行`else`子句。\n",
    "\n",
    "\n",
    "#### `finally`子句\n",
    "\n",
    "```\n",
    "try:\n",
    "    ...\n",
    "except MyException:\n",
    "    ...\n",
    "else:\n",
    "    ...\n",
    "finally:\n",
    "    ...\n",
    "```\n",
    "\n",
    "**无论异常是否发生，无论异常是否被捕捉到，`finally`后的语句块一定会被执行。**诸如关闭文件，断开数据库连接之类的语句理所当然应当放在这一部分。\n",
    "\n",
    "#### `try-finally`子句\n",
    "\n",
    "```\n",
    "try:\n",
    "    try_suite\n",
    "finally:\n",
    "    finally_suite\n",
    "```\n",
    "\n",
    "这种用法和`try-except`的区别主要在于它不是用来捕捉异常的，而是用于保证无论异常是否发生，`finally`代码段都会执行。\n",
    "\n",
    "#### 综合\n",
    "\n",
    "```\n",
    "try:\n",
    "    try_suite\n",
    "except Exception1:\n",
    "    suite_for_exception1\n",
    "except (Exception2, Exc3, Exc4):\n",
    "    suite_for_exc2_to_4\n",
    "except (Exc5, Exc6) as argu_for_56:\n",
    "    suite_for_exc5_to_6\n",
    "except:\n",
    "    suite_for_other_exceptions\n",
    "else:\n",
    "    no_exception_detected_suite\n",
    "finally:\n",
    "    always_excute_suite\n",
    "```\n",
    "\n",
    "### 触发异常\n",
    "\n",
    "到目前为止，看到的异常都是由Python解释器引起的，由执行期间的错误引发。很多情况下，你编写自己的类或者API，需要在遇到错误输入的时候触发异常，Python提供了`raise`语句来实现这一机制。\n",
    "\n",
    "语法：\n",
    "```\n",
    "raise [expression [from expression]]\n",
    "```\n",
    "\n",
    "如果`raise`后面没有跟表达式，`raise`将重新引发当前作用域中的最后一个异常。如果当前作用域中没有异常，将引发一个`RuntimeError`来提示有错误发生。\n",
    "\n",
    "其他情况下，`raise`将执行第一个表达式作为异常对象。它必须是`BaseException`的子类或者一个实例。如果它是一个类，那么将通过不带任何参数的初始化这个类来获取其实例对象。\n",
    "\n",
    "`from`语句被用来进行异常链追踪，其后的表达式必须是另外一个异常类或者实例。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    print(1 / 0)\n",
    "except Exception as exc:\n",
    "    raise RuntimeError(\"Something bad happened\") from exc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    print(1 / 0)\n",
    "except:\n",
    "    raise RuntimeError(\"Something bad happened\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "### 创建异常\n",
    "当系统的内建异常不能满足你的需求时，就需要动手创建自己的异常。异常的创建实质是异常类的设计。这里涉及到了面向对象编程中类的设计，因此不再这里讲述，在后面会讲到。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "class UppercaseException(Exception):\n",
    "    pass\n",
    "\n",
    "words = ['eenie', 'meenie', 'miny', 'MO']\n",
    "for word in words:\n",
    "    if word.isupper():\n",
    "        raise UppercaseException(word)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "学习异常就是学习如何解决程序中的各种有意、无意引入的错误，这对于提高程序健壮性，减少bugs是非常重要的。程序设计中，使用异常框架来管理诸如数据库连接，文件操作，GUI响应等极容易发生异常的过程是必须的。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  },
  "name": "6_exceptions.ipynb"
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
